﻿using System.Diagnostics;
using System.IO.Compression;
using Test.Services.IServices;

namespace Test.Services.Implementation;

public class DecryptService : IDecryptService {
    // ******** 公开变量

    // ******** 私有变量

    /// <summary>
    /// 临时后缀
    /// </summary>
    private const string TempSuffix = ".1q2q3q4q5q6q";

    /// <summary>
    /// 临时后缀
    /// </summary>
    private const string ZipSuffix = ".zip";

    private readonly ILogService _logService;

    // ******** 继承方法

    public event EventHandler<DecryptEventArgs>? Decrypt;

    public async Task<bool> DecryptOneAsync(string encryptedFilePath,
        string encryptedFileName) {
        //生成解密后的临时文件
        File.Copy(encryptedFilePath, encryptedFilePath + TempSuffix);

        //删除被加密文件
        try {
            File.Delete(encryptedFilePath);
        } catch (Exception e) {
            _logService.WriteLog(nameof(DecryptService), LogType.Error,
                $"{nameof(DecryptService)}.{nameof(DecryptOneAsync)}",
                $"删除被加密文件失败,文件={encryptedFilePath},异常信息={e.Message}");
            Decrypt?.Invoke(this,
                new DecryptEventArgs {
                    FileName = encryptedFileName, DecryptSuccessful = false
                });
            return false;
        }
        
        try {
            // 压缩解密后的临时文件
            var zipFilePath = CompressFile(encryptedFilePath + TempSuffix);
        
            // 在压缩包中修改临时文件名称为原被加密文件的名称
            var decryptedFileName = encryptedFileName;
            ModifyFileNameInZip(zipFilePath, encryptedFileName + TempSuffix,
                decryptedFileName);
        
            var command = "tar";
            var arguments = $"-xzvf {encryptedFilePath}{TempSuffix}{ZipSuffix} -C {encryptedFilePath.Replace(encryptedFileName,string.Empty)}";
        
            ExecuteShellCommand(command, arguments);
        
            //删除临时文件和压缩包
            File.Delete($"{encryptedFilePath}{TempSuffix}");
            File.Delete(zipFilePath);
        } catch (Exception e) {
            _logService.WriteLog(nameof(DecryptService), LogType.Error,
                $"{nameof(DecryptService)}.{nameof(DecryptOneAsync)}",
                $"解密文件失败,文件={encryptedFilePath},异常信息={e.Message}");
            Decrypt?.Invoke(this,
                new DecryptEventArgs {
                    FileName = encryptedFileName, DecryptSuccessful = false
                });
            return false;
        }

        Decrypt?.Invoke(this,
            new DecryptEventArgs {
                FileName = encryptedFileName, DecryptSuccessful = true
            });

        return true;
    }

    public async Task<bool> DecryptDirectoryAsync(string directory) {
        var files = Directory
            .GetFiles(directory, "*.*", SearchOption.AllDirectories)
            .Select(filePath => new FileInfo(filePath)).ToList();
        files.ForEach(async f => await DecryptOneAsync(f.FullName, f.Name));
        foreach (var file in files) {
            var decryptSuccessful =
                await DecryptOneAsync(file.FullName, file.Name);
            if (!decryptSuccessful)
                return false;
        }

        return true;
    }

    // ******** 公开方法

    public DecryptService(ILogService logService) {
        _logService = logService;
    }

    // ******** 私有方法

    private string CompressFile(string filePath) {
        var zipFilePath = $"{filePath}{ZipSuffix}";

        using (var fileStream = new FileStream(zipFilePath, FileMode.Create))
        using (var archive =
               new ZipArchive(fileStream, ZipArchiveMode.Create)) {
            // 将文件添加到压缩包中
            var entry = archive.CreateEntry(Path.GetFileName(filePath));

            using (var entryStream = entry.Open())
            using (var fileToCompress =
                   new FileStream(filePath, FileMode.Open)) {
                fileToCompress.CopyTo(entryStream);
            }
        }

        return zipFilePath;
    }

    private void ModifyFileNameInZip(string zipFilePath, string oldFileName,
        string newFileName) {
        var tempZipFilePath = "temp.zip";

        using (var fileStream = new FileStream(zipFilePath, FileMode.Open))
        using (var oldArchive = new ZipArchive(fileStream, ZipArchiveMode.Read))
        using (var tempFileStream =
               new FileStream(tempZipFilePath, FileMode.Create))
        using (var newArchive =
               new ZipArchive(tempFileStream, ZipArchiveMode.Create)) {
            foreach (var entry in oldArchive.Entries)
                // 复制旧的 entry 数据到新的 entry
                if (entry.FullName != oldFileName) {
                    var newEntry = newArchive.CreateEntry(entry.FullName);

                    using (var newEntryStream = newEntry.Open())
                    using (var entryStream = entry.Open()) {
                        entryStream.CopyTo(newEntryStream);
                    }
                } else {
                    // 修改文件名称
                    var newEntry = newArchive.CreateEntry(newFileName);

                    using (var newEntryStream = newEntry.Open())
                    using (var entryStream = entry.Open()) {
                        entryStream.CopyTo(newEntryStream);
                    }
                }
        }

        // 删除原始压缩文件，将临时文件重命名为原始文件
        File.Delete(zipFilePath);
        File.Move(tempZipFilePath, zipFilePath);
    }

    private void ExecuteShellCommand(string command, string arguments) {
        var psi = new ProcessStartInfo {
            FileName = command,
            Arguments = arguments,
            RedirectStandardOutput = true,
            RedirectStandardError = true,
            UseShellExecute = false,
            CreateNoWindow = true
        };

        using (var process = Process.Start(psi)) {
            if (process != null) {
                var output = process.StandardOutput.ReadToEnd();
                var error = process.StandardError.ReadToEnd();

                process.WaitForExit();

                var exitCode = process.ExitCode;

                Console.WriteLine($"Command exited with code {exitCode}");
                Console.WriteLine("Output:");
                Console.WriteLine(output);
                Console.WriteLine("Error:");
                Console.WriteLine(error);
            }
        }
    }
}